/**
 * trayinfo.c
 * Provides periodical information for the system tray of dwm, wmii, awesome...
 * Created by Laszlo Ashin <kodest@gmail.com>
 */

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>

#include "trayinfo.h"

#ifdef HAVE_LIBSSL
# include <openssl/ssl.h>
#endif /* HAVE_LIBSSL */

static struct timeval delay = { 1, 0 };

static src_regfunc_t regfuncs[] = { SRCREGFUNCS };
#define NSOURCES (sizeof(regfuncs) / sizeof(regfuncs[0]))

static src_t srcs[NSOURCES];

static int active_srcs;
static int reload_requested = 0;
static int quit_requested = 0;

typedef struct cfg_s {
	struct cfg_s *next;
	const char *name;
	cfg_handler_t handler;
} cfg_t;

static cfg_t *firstcfg;
static char *separator = NULL;

/**
 * a = a - b;
 * if a < b then returns 1, otherwise 0
 */
static int
timeval_subtract(struct timeval *a, struct timeval *b)
{
	if (a->tv_sec < b->tv_sec) return 1;
	a->tv_sec -= b->tv_sec;
	if (a->tv_usec < b->tv_usec) {
		if (!a->tv_sec) return 1;
		--a->tv_sec;
		a->tv_usec = 1000000 - b->tv_usec + a->tv_usec;
	} else
		a->tv_usec -= b->tv_usec;

	if (a->tv_usec > 1000000) {
		a->tv_sec += a->tv_usec / 1000000;
		a->tv_usec %= 1000000;
	}
	return 0;
}

/**
 * reload signal handler
 */
static void
signal_sigusr1(int sig_num)
{
	reload_requested = 1;
}

/**
 * kill signal handler
 */
static void
signal_kill(int sig_num)
{
	quit_requested = 1;
}

/**
 * config handlers for default config items
 */

static int
cfg_delay(const char *s)
{
	double d;

	if (sscanf(s, "%lf", &d) != 1) {
		return 1;
	}
	delay.tv_sec = (unsigned)d;
	delay.tv_usec = (unsigned)(1000000.0f * (d - (unsigned)d)) % 1000000;
	return 0;
}

static int
cfg_separator(const char *s)
{
	if (separator) free(separator);
	separator = strdup(s);
	return 0;
}

static int
activate_src(const char *name)
{
	int i;

	for (i = 0; i < NSOURCES; ++i)
		if (!strcmp(srcs[i].name, name)) {
			if (i >= active_srcs) {
				if (i != active_srcs) {
					src_t tmp = srcs[active_srcs];
					srcs[active_srcs] = srcs[i];
					srcs[i] = tmp;
				}

				++active_srcs;
			}
			return 0;
		}

	fprintf(stderr, "No such source: %s\n", name);
	return 1;
}

void
cfg_add_handler(const char *name, cfg_handler_t handler)
{
	cfg_t *cfg = (cfg_t *)malloc(sizeof(cfg_t));
	if (!cfg) return;
	cfg->next = firstcfg;
	cfg->name = name;
	cfg->handler = handler;
	firstcfg = cfg;
}

static void
setup_srcs(void)
{
	int i;
	src_t *src;

	firstcfg = NULL;
	cfg_add_handler("delay", cfg_delay);
	cfg_add_handler("add", activate_src);
	cfg_add_handler("separator", cfg_separator);

	for (i = NSOURCES, src = srcs; i; --i, ++src) {
		/* init all the fields in case of a careless source */
		src->name = "";
		src->init = NULL;
		src->done = NULL;
		src->write = NULL;
		src->open = NULL;
		src->close = NULL;
		src->read = NULL;

		src->fd = 0;
		src->last_retry = 0;
		src->wait_retry = WAIT_RETRY;

		regfuncs[i - 1](src);
	}

#if DEBUG
	{
		cfg_t *cf;

		puts("Registered handlers:");
		for (cf = firstcfg; cf; cf = cf->next)
			puts(cf->name);
	}
#endif
}

static void
shutdown_srcs(void)
{
	while (firstcfg) {
		cfg_t *cfg = firstcfg;
		firstcfg = cfg->next;
		free(cfg);
	}
}

static void
load_cfg(void)
{
	char buf[80];
	char *s = getenv("HOME");
	size_t i = strlen(s) + strlen(CONFIG_FNAME) + 1;
	char *fname;
	FILE *f;

	if (i > 256) return;
	fname = malloc(i);
	if (!fname) return;
	strcpy(fname, s);
	strcat(fname, CONFIG_FNAME);
	f = fopen(fname, "r");
	if (!f) return;
	i = 0;
	while (fgets(buf, sizeof(buf), f) == buf) {
		char *t;
		cfg_t *cfg;

		++i;
		for (s = buf; *s && *s < 33; ++s);
		if (!*s || *s == '#') continue;

		for (t = s + strlen(s) - 1; t >= s && *t < 33; *t-- = '\0');
		t = s;
		while (*s && *s > 32) ++s;
		if (*s) *s++ = '\0';
		if (*s == '"') {
			char *u = s + strlen(s) - 1;
			++s;
			if (*u != '"') {
				fprintf(
					stderr,
					"%s:%d: quotes must be closed\n",
					fname, i
				);
				continue;
			}
			*u = '\0';
		}

		for (cfg = firstcfg; cfg; cfg = cfg->next)
			if (!strcmp(cfg->name, t)) {
				if (cfg->handler(s))
					fprintf(
						stderr,
						"%s:%d: a mistake was found\n",
						fname, i
					);
				break;
			}

		if (!cfg)
			fprintf(
				stderr, "%s:%d: no such cfg action (%s)\n",
				fname, i, t
			);
	}
	fclose(f);
	free(fname);
}

static void
init_srcs(void)
{
	int i;

	for (i = 0; i < NSOURCES; ++i)
		if (srcs[i].init) srcs[i].init();

	active_srcs = 0;
	load_cfg();

	for (i = 0; i < active_srcs; ++i)
		if (srcs[i].open) {
			srcs[i].fd = srcs[i].open();
			srcs[i].last_retry = time(NULL);
		}
}

static void
done_srcs(void)
{
	int i;

	for (i = 0; i < active_srcs; ++i)
		if (srcs[i].close) {
			srcs[i].close(srcs[i].fd);
			srcs[i].fd = 0;
		}

	for (i = 0; i < NSOURCES; ++i)
		if (srcs[i].done) srcs[i].done();

	active_srcs = 0;

	if (separator) free(separator);
	separator = NULL;
}

void
put_separator(void)
{
	if (separator)
		fwrite(separator, strlen(separator), 1, stdout);
	else
		putchar(32);
}

int
main(int argc, char *argv[])
{
	struct timeval tv0, tv;

	setlinebuf(stdout);
	puts("TrayInfo is initializing...");

#ifdef HAVE_LIBSSL
	SSL_library_init();
#endif /* HAVE_LIBSSL */

	{
		struct sigaction sa;

		sa.sa_handler = signal_sigusr1;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = 0;
		sigaction(SIGUSR1, &sa, NULL);

		sa.sa_handler = signal_kill;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = 0;
		sigaction(SIGTERM, &sa, NULL);
		sigaction(SIGINT, &sa, NULL);
	}

	setup_srcs();
	init_srcs();

	tv = delay;
	gettimeofday(&tv0, NULL);
	do {
		struct timeval tv1, now;
		fd_set rfds;
		int i, maxfd = -1;
		src_t *s;

		FD_ZERO(&rfds);
		s = srcs;
		for (i = active_srcs; i; --i, ++s) {
			/* try to reconnect a disconnected source */
			if (!s->fd && s->open &&
			    (time(NULL) >= s->last_retry + s->wait_retry)) {
				s->fd = s->open();
				s->last_retry = time(NULL);
			}
			/* add an active source to select()'s readfd list */
			if (s->fd > 0) {
				FD_SET(s->fd, &rfds);

				if (s->fd > maxfd)
					maxfd = s->fd;
			}
		}

		i = select(maxfd + 1, &rfds, NULL, NULL, &tv);
		if (i > 0) {
			s = srcs;
			for (i = active_srcs; i; --i, ++s)
				if (FD_ISSET(s->fd, &rfds)) {
					if (s->read(s->fd) <= 0) {
						s->close(s->fd);
						s->fd = 0;
					}
					break;
				}
		}

		gettimeofday(&now, NULL);
		tv1 = now;
		timeval_subtract(&tv1, &tv0);
		tv = delay;
		if (timeval_subtract(&tv, &tv1) ||
		    (!tv.tv_sec && tv.tv_usec < 10000)) {
			s = srcs;
			for (i = active_srcs; i; --i, ++s)
				if (s->write(&now, s->fd) && i > 1)
					put_separator();

			putchar('\n');

			tv = delay;
			gettimeofday(&tv0, NULL);
		}

		if (reload_requested) {
			done_srcs();
			init_srcs();
			reload_requested = 0;
		}
	} while (!quit_requested);

	puts("TrayInfo is exiting...");

	done_srcs();
	shutdown_srcs();

	return 0;
}
